package tk.mynoteapp.dev.demo03.threadpoolmixuse;

import jodd.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalTime;
import java.util.Collections;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;

/**
 * 需要仔细斟酌线程池的混用策略
 * 线程池的意义在于复用，那这是不是意味着程序应该始终使用一个线程池呢？ 当然不是，因为计算任务的不同，混用将导致相互干扰
 *  1、要根据任务的“轻重缓急”来指定线程池的核心参数，包括线程数、回收策略和任务队列（可以根据任务类型的不同使用不同的线程池）
 *    - 【IO密集型：多线程数，小队列】对于执行比较慢、数量不大的 IO 任务，或许要考虑更多的线程数，而不需要太大的队列。
 *
 *    - 【CPU密集型：少线程数，大队列】对于吞吐量较大的计算型任务，线程数量不宜过多，可以是 CPU 核数或核数 *2（理由是，
 *      线程一定调度到某个 CPU 进行执行，如果任务本身是 CPU 绑定的任务，那么过多的线程只会
 *      增加线程切换的开销，并不能提升吞吐量），但可能需要较长的队列来做缓冲。
 */
@RestController
@RequestMapping("threadpoolmixuse")
public class ThreadPoolMixuseController {
    private Logger logger = LoggerFactory.getLogger(ThreadPoolMixuseController.class);


    private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
            2, 2,
            1, TimeUnit.HOURS,
            new ArrayBlockingQueue<>(100),
            new ThreadFactoryBuilder().setNameFormat("batchfileprocess-threadpool-%d").get(),
            new ThreadPoolExecutor.CallerRunsPolicy()
    );

    private static ThreadPoolExecutor asyncCalcThreadPool = new ThreadPoolExecutor(
            200, 200,
            1, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1000),
            new ThreadFactoryBuilder().setNameFormat("asynccalc-threadpool-%d").get()
    );

    private void printStats(ThreadPoolExecutor threadPool) {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            logger.info("=========================");
            logger.info("Pool Size: {}", threadPool.getPoolSize());
            logger.info("Active Threads: {}", threadPool.getActiveCount());
            logger.info("Number of Tasks Completed: {}", threadPool.getCompletedTaskCount());
            logger.info("Number of Tasks in Queue: {}", threadPool.getQueue().size());
            logger.info("=========================");
        }, 0, 1, TimeUnit.SECONDS);
    }

    private Callable<Integer> calcTask() {
        return () -> {
            TimeUnit.MILLISECONDS.sleep(10);
            return 1;
        };
    }

    /**
     * 线程复用问题：threadPool线程池被用来执行I/O密集型的任务，
     *
     */
    @GetMapping("wrong")
    public int wrong() throws ExecutionException, InterruptedException {
        // calcTask()方法里使用的是休眠操作（类似IO操作不消耗CPU），属于IO密集型可以加大线程数和任务队列容量
        return threadPool.submit(calcTask()).get();
    }

    @GetMapping("right")
    public int right() throws ExecutionException, InterruptedException {
        return asyncCalcThreadPool.submit(calcTask()).get();
    }

    // 模拟一下文件批处理的代码，在程序启动后通过一个线程开启死循环逻辑，不断向线程池提交任务，任务的逻辑是向一个文件中写入大量的数据：
//    @PostConstruct
    public void init() {
        printStats(threadPool);

        new Thread(() -> {
            // 模拟写入大量数据
            String payload = IntStream.rangeClosed(1, 1_000_000)
                    .mapToObj(__ -> "a")
                    .collect(Collectors.joining(""));
            while (true) {
                threadPool.execute(() -> {
                    try {
                        // 每次都是创建并写入相同的数据到相同的文件
                        Files.write(Paths.get("demo.txt"), Collections.singletonList(LocalTime.now().toString() + ":" + payload), UTF_8, CREATE, TRUNCATE_EXISTING);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    logger.info("batch file processing done");
                });
            }
        }).start();
    }

}
